#pragma once

#include "eval_func.cpp"

#include <iostream>
#include <iomanip>
#include <fstream>

#include <cstdlib>
#include <cstring>

#include <cmath>

// clear a board, set it to empty
I_CHESS_POSITION* empty_board()
{
	// return value
	I_CHESS_POSITION *p = new I_CHESS_POSITION;

	// copy blank board into the new value
	memcpy(p->brd, &BLANK_BOARD, sizeof(int) * I_BOARD_RANK * I_BOARD_RANK);

	// set flags and player
	p->col = I_SIDE_WHITE;
	p->enp = 0; p->cst = NO_CASTLING;

	// reset pawn counts
	memset(p->pwn, NO_PAWNS, 2);

	// set move clocks
	p->fullmoves = 1;
	p->halfmoves = 0;

	// is this position located in the opening book?
	p->open = false;

	// return this
	return p;
};

// setup a board in the standard starting position
I_CHESS_POSITION* start_board()
{
	// return value
	I_CHESS_POSITION *p = new I_CHESS_POSITION;

	// copy starting board into the new value
	memcpy(p->brd, &START_BOARD, sizeof(int) * I_BOARD_RANK * I_BOARD_RANK);

	// set flags and player
	p->col = I_SIDE_WHITE;
	p->enp = p->cst = 0;
	
	// generate hash and lock
	p->hash = board_hash(p, p->col);
	p->lock = board_lock(p, p->col);

	// reset pawn counts
	memset(p->pwn, ALL_PAWNS, 2);

	// set the king locations
	p->king[I_SIDE_WHITE] = get_index("e1");
	p->king[I_SIDE_BLACK] = get_index("e8");

	// set move clocks
	p->fullmoves = 1;
	p->halfmoves = 0;

	// evaluate counters
	eval_material_value(p);

	// is this position located in the opening book?
	p->open = true;

	// return this
	return p;
};

void free_board(I_CHESS_POSITION *p)
{ delete p; };

// evaluate the material value and piece position strength of a given board
void eval_material_value(I_CHESS_POSITION *p)
{
	// initialization
	p->mat[I_SIDE_WHITE] = p->mat[I_SIDE_BLACK] = 0;
	p->pos[I_SIDE_WHITE] = p->pos[I_SIDE_BLACK] = 0;

	// for all squares on the board (with pieces)
	for (int i = 0; i < I_BOARD_SIZE; ++i) if (p->brd[i] < I_BOARD_EMPTY)
	{
		// add up piece values
		p->mat[p->brd[i] & 1] += GAME.PIECE_VALUE[p->brd[i] >> 1];

		// add up position strength
		p->pos[p->brd[i] & 1] += GAME.POS_BOARDS[p->brd[i]][i];
	}
};

// apply a move to a board
void apply_move(I_CHESS_POSITION *p, I_CHESS_MOVE *m)
{ memcpy(p, m->board[MOVE_TO  ], sizeof(I_CHESS_POSITION)); };

// undo a move from a board
void undo_move(I_CHESS_POSITION *p, I_CHESS_MOVE *m)
{ memcpy(p, m->board[MOVE_FROM], sizeof(I_CHESS_POSITION)); };

// apply a move to a board (from string)
void apply_move(I_CHESS_POSITION *p, C_STR mv_str)
{
	// generate all legal moves
	MOVELIST moves = gen_move_list(p);
	
	// find whether the move specified is a legal move and in this list
	int i; for (i = 0; i < moves.size; ++i)
	if (STR_EQUAL(mv_str, move_to_coord(moves.arr[i]).str)) break;
	
	// assert that the string is a valid move
	trace_assert(i < moves.size, "FAILED to generate a valid move from the string provided: ", mv_str);
	
	// apply the move we found
	apply_move(p, moves.arr[i]);
	
	// empty out the movelist object
	free_move_list(moves);
};

// fill the arrays with random integers to help with the keys and locks
void generate_zobrist(ZOBRIST *keys, ZOBRIST *lock)
{
	// for all board squares
	for (int i = 0; i < I_BOARD_TRUE_RANK; ++i)
	for (int g = 0; g < I_BOARD_TRUE_RANK; ++g)
	{
		// reciprocal square from the black perspective
		int k = I_BOARD_TRUE_RANK - (i + 1);
		
		// for all actual pieces
		for (int j = 0; j < I_PIECE_COUNT; ++j)
		{
			// the exclusive-or trick is a bit of magic to make the perspective
			// of the pieces on the board relevant to both players
			keys[I_SIDE_BLACK][k][g][j ^ 1] = keys[I_SIDE_WHITE][i][g][j] = mt_rand32();
			lock[I_SIDE_BLACK][k][g][j ^ 1] = lock[I_SIDE_WHITE][i][g][j] = mt_rand32();
		};
		
		// blank out the "empty square" value
		keys[I_SIDE_BLACK][k][g][I_BOARD_EMPTY] = 0U;
		keys[I_SIDE_WHITE][i][g][I_BOARD_EMPTY] = 0U;
	};
};

// compute the hash key given a specific board position
ZRANDOM board_hash(I_CHESS_POSITION *p, int c)
{ return board_hashelem(p, GAME.ZKEYS[c]); };

// compute the hash lock given a specific board position
ZRANDOM board_lock(I_CHESS_POSITION *p, int c)
{ return board_hashelem(p, GAME.ZLOCK[c]); };

// compute the hash lock given a specific board position
ZRANDOM board_hashelem(I_CHESS_POSITION *p, ZOBRIST z)
{
	// temporary variables
	int i, j, k = 0; ZRANDOM hash = 0;

	// generate the hash using the table specified
	for (i = I_BOARD_BEGIN; i < I_BOARD_END; ++i)
	for (j = I_BOARD_BEGIN; j < I_BOARD_END; ++j)
	hash ^= z[0][k++][p->brd[i * I_BOARD_RANK + j]]; 
	
	// return the hash value
	return hash;
}

// setup an I_CHESS_MOVE object
inline I_CHESS_MOVE* setup_move(I_CHESS_POSITION *p)
{
	// create move object
	I_CHESS_MOVE* temp_mv = new I_CHESS_MOVE;

	// create boardspace
	temp_mv->board[MOVE_FROM] = new I_CHESS_POSITION;
	temp_mv->board[MOVE_TO  ] = new I_CHESS_POSITION;

	// copy "to" and "from" boards
	memcpy(temp_mv->board[MOVE_FROM], p, sizeof(I_CHESS_POSITION));
	memcpy(temp_mv->board[MOVE_TO  ], p, sizeof(I_CHESS_POSITION));

	// switch player
	temp_mv->board[MOVE_TO]->col ^= 1;

	// reset en passant flags
	temp_mv->board[MOVE_TO]->enp = NO_EN_PASSANT;

	// update halfmove clock
	++temp_mv->board[MOVE_TO]->halfmoves;

	// update fullmove clock
	if (temp_mv->board[MOVE_TO]->col == I_SIDE_WHITE)
	++temp_mv->board[MOVE_TO]->fullmoves;

	// setup
	return temp_mv;
}

void free_move(I_CHESS_MOVE* mv)
{
	// first, delete the chess boards
	// free_board(mv->board[MOVE_FROM]);
	// free_board(mv->board[MOVE_TO  ]);
	
	// finally, delete the move object
	delete mv;
};

// convert an I_CHESS_MOVE object to a string (long algebraic notation)
MOVE_NAME move_to_algL(I_CHESS_MOVE* mv)
{
	// return string
	MOVE_NAME ret_str = {0}; int i = 0;
	
	// if this is a castling move, print the corresponding string
	if 		(mv->str & I_MOVE_CASTLE_KI){ strcpy(ret_str.str, "O-O"  );	i += 3; }
	else if	(mv->str & I_MOVE_CASTLE_QU){ strcpy(ret_str.str, "O-O-O");	i += 5; }
	// otherwise, print the move
	else
	{
		// get the piece being moved
		int moved_piece = mv->board[MOVE_FROM]->brd[mv->index[MOVE_FROM]];
		
		// note the piece that is being moved
		if (!is_piece_type(moved_piece, I_PIECE_PA))
		ret_str.str[i++] = toupper(PIECE_CHARS[moved_piece]);
		
		// print the location from which we are moving
		write_coord(mv->index[MOVE_FROM], ret_str.str + i); i += 2;
		
		// print the appropriate separator
		ret_str.str[i++] = (mv->str & I_MOVE_CAPTURE) ? 'x' : '-';
		
		// print the location to which we are moving
		write_coord(mv->index[MOVE_TO  ], ret_str.str + i); i += 2;
	};
	
	// if this move causes check or checkmate, print the appropriate designator
	if (checkmate(mv->board[MOVE_TO])) 			ret_str.str[i++] = '#';
	else if (in_check(mv->board[MOVE_TO])) 		ret_str.str[i++] = '+';
	
	// return long algebraic form
	return ret_str;
};

// convert an I_CHESS_MOVE object to a string (standard coordinate notation)
MOVE_NAME move_to_coord(I_CHESS_MOVE* mv)
{
	// return string
	MOVE_NAME ret_str = {0}; int i = 0;
	
	// if this is a castling move, print the corresponding string
	if 		(mv->str & I_MOVE_CASTLE_KI){ strcpy(ret_str.str, "O-O"  );	i += 3; }
	else if	(mv->str & I_MOVE_CASTLE_QU){ strcpy(ret_str.str, "O-O-O");	i += 5; }
	// otherwise, print the move
	else
	{
		// print the location from which we are moving
		write_coord(mv->index[MOVE_FROM], ret_str.str + i); i += 2;
		
		// print the location to which we are moving
		write_coord(mv->index[MOVE_TO  ], ret_str.str + i); i += 2;
	};
	
	// return standard coordinate form
	return ret_str;
};

// setup an null move object
inline I_CHESS_MOVE* null_move(I_CHESS_POSITION *p)
{ I_CHESS_MOVE* nm = setup_move(p); nm->str = I_MOVE_NULL; return nm; };

// generate the movelist for a given board
MOVELIST gen_move_list(I_CHESS_POSITION *p)
{
	// return value
	MOVELIST return_moves; return_moves.size = 0;
	return_moves.arr = new I_CHESS_MOVE*[MOVELIST_MAX_SIZE];
	
	I_BOARD_2D* b = (I_BOARD_2D*) p->brd;
	int i, j, pt, cp = p->col, op = cp ^ 1;

	for (i = I_BOARD_BEGIN; i < I_BOARD_END; ++i) for (j = I_BOARD_BEGIN; j < I_BOARD_END; ++j)
	if (((pt = (*b)[i][j]) < I_PIECE_COUNT) && ((pt & 1) == cp)) switch(pt & ~1)
	{
		case I_PIECE_PA: add_legal_moves_PA(p, i * I_BOARD_RANK + j, return_moves); break;
		case I_PIECE_KN: add_legal_moves_KN(p, i * I_BOARD_RANK + j, return_moves); break;
		case I_PIECE_BI: add_legal_moves_BI(p, i * I_BOARD_RANK + j, return_moves); break;
		case I_PIECE_QU: add_legal_moves_BI(p, i * I_BOARD_RANK + j, return_moves); // get both BI moves and RO moves...
		case I_PIECE_RO: add_legal_moves_RO(p, i * I_BOARD_RANK + j, return_moves); break;
		case I_PIECE_KI: add_legal_moves_KI(p, i * I_BOARD_RANK + j, return_moves); break;
	};

	// give precedence to moves that are in the opening book
	if (USE_OPENING_BOOKS && p->open) for (i = 0; i < return_moves.size; ++i) if (binary_search<ZRANDOM>(GAME.OPEN_BOOK, board_hash(return_moves.arr[i]->board[MOVE_TO], op)) == -1)
	{ return_moves.arr[i]->board[MOVE_TO]->open = false; } else { return_moves.arr[i]->str |= I_MOVE_OPEN_BOOK; };

	std::sort(return_moves.arr, return_moves.arr + return_moves.size, MOVE_COMPARE);
	return return_moves;
};

inline void free_move_list(MOVELIST m)
{ while (m.size > 0) free_move(m.arr[--m.size]); delete [] m.arr; };

// add all legal moves for the current players' pawn
// pawns' legal moves are only capturing along a diagonal and moving forward
void add_legal_moves_PA(I_CHESS_POSITION *p, int k, MOVELIST& moves)
{
	int cp = p->col, dir = I_FORWARD[cp], pr = I_PAWNROW[cp], dest; I_CHESS_MOVE *mv;
	int op = 1 ^ cp, otherpawn = I_PIECE_PA + op, promote_row = I_PROMROW[cp], tenp;
	
	int const this_piece = I_PIECE_PA + cp;

	// MOVE #1: move forward one space (standard)
	if (p->brd[dest = k + (dir * I_BOARD_RANK)] == I_BOARD_EMPTY)
	{
		// made it to opponent's back row
		if (dest / I_BOARD_RANK == promote_row)
		{
			// promote to knight
			mv = setup_move(p);

			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;
			mv->board[MOVE_TO]->pwn[cp]		= mv->board[MOVE_FROM]->pwn[cp] - 1;

			mv->board[MOVE_TO]->mat[cp] 	-= GAME.PIECE_VALUE[this_piece];
			mv->board[MOVE_TO]->mat[cp] 	+= GAME.PIECE_VALUE[I_PIECE_KN + cp];

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[I_PIECE_KN + cp][dest];

			mv->board[MOVE_TO]->brd[dest] 	= I_PIECE_KN + cp;
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_PROMOTE_KN;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;

			// promote to queen (bishop and rook are underpromotions)
			mv = setup_move(p);

			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;
			mv->board[MOVE_TO]->pwn[cp]		= mv->board[MOVE_FROM]->pwn[cp] - 1;

			mv->board[MOVE_TO]->mat[cp] 	-= GAME.PIECE_VALUE[this_piece];
			mv->board[MOVE_TO]->mat[cp] 	+= GAME.PIECE_VALUE[I_PIECE_QU + cp];

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[I_PIECE_QU + cp][dest];

			mv->board[MOVE_TO]->brd[dest] 	= I_PIECE_QU + cp;
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_PROMOTE_KN;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;
		}
		else
		{
			// normal move forward (no promotion)
			mv = setup_move(p);
			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

			mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[this_piece][dest];

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;
		}
	}

	// MOVE #2: move forward two spaces (if first move)
	if (k / I_BOARD_RANK == pr)
	if (p->brd[tenp = k + (dir     * I_BOARD_RANK)] == I_BOARD_EMPTY)
	if (p->brd[dest = k + (dir * 2 * I_BOARD_RANK)] == I_BOARD_EMPTY)
	{
		// bool for en passant
		bool enpass = (p->brd[dest + 1] == otherpawn) || (p->brd[dest - 1] == otherpawn);

		// create move object
		mv = setup_move(p);
		mv->board[MOVE_TO]->enp 		= enpass ? tenp : NO_EN_PASSANT;

		mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
		mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

		mv->index[MOVE_TO  ]			= dest;
		mv->index[MOVE_FROM]			= k;

		mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
		mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[this_piece][dest];

		mv->board[MOVE_TO]->halfmoves	= 0;
		mv->str = I_MOVE_NORMAL;

		// only add this move to the stack if does not cause check
		if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
		else moves.arr[moves.size++] = mv;
	}

	// MOVE #3: capture a piece to the left
	if (p->brd[dest = k + (dir * I_BOARD_RANK) - 1] < I_PIECE_COUNT)
	if ((p->brd[dest] & 1) == op)
	{
		// made it to opponent's back row
		if (dest / I_BOARD_RANK == promote_row)
		{
			// promote to knight
			mv = setup_move(p);

			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;
			mv->board[MOVE_TO]->pwn[cp]		= mv->board[MOVE_FROM]->pwn[cp] - 1;

			mv->board[MOVE_TO]->mat[cp] 	-= GAME.PIECE_VALUE[this_piece];
			mv->board[MOVE_TO]->mat[cp] 	+= GAME.PIECE_VALUE[I_PIECE_KN + cp];

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[I_PIECE_KN + cp][dest];

			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];
			mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[dest]][dest];

			mv->board[MOVE_TO]->brd[dest] 	= I_PIECE_KN + cp;
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_PROMOTE_KN | I_MOVE_CAPTURE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;

			// promote to queen (bishop and rook are underpromotions)
			mv = setup_move(p);

			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;
			mv->board[MOVE_TO]->pwn[cp]		= mv->board[MOVE_FROM]->pwn[cp] - 1;

			mv->board[MOVE_TO]->mat[cp] 	-= GAME.PIECE_VALUE[this_piece];
			mv->board[MOVE_TO]->mat[cp] 	+= GAME.PIECE_VALUE[I_PIECE_QU + cp];

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[I_PIECE_KN + cp][dest];

			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];
			mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[dest]][dest];

			mv->board[MOVE_TO]->brd[dest] 	= I_PIECE_KN + cp;
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_PROMOTE_QU | I_MOVE_CAPTURE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;
		}
		else
		{
			mv = setup_move(p);
			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

			// if we are taking a pawn, we need to account for it
			if (mv->board[MOVE_FROM]->brd[dest] == otherpawn) --mv->board[MOVE_TO]->pwn[op];

			mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[this_piece][dest];

			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];
			mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[dest]][dest];

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_CAPTURE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;
		}
	}

	// MOVE #4: capture a piece to the right
	if (p->brd[dest = k + (dir * I_BOARD_RANK) + 1] < I_PIECE_COUNT)
	if ((p->brd[dest] & 1) == op)
	{
		// made it to opponent's back row
		if (dest / I_BOARD_RANK == promote_row)
		{
			// promote to knight
			mv = setup_move(p);

			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;
			mv->board[MOVE_TO]->pwn[cp]		= mv->board[MOVE_FROM]->pwn[cp] - 1;

			mv->board[MOVE_TO]->mat[cp] 	-= GAME.PIECE_VALUE[this_piece];
			mv->board[MOVE_TO]->mat[cp] 	+= GAME.PIECE_VALUE[I_PIECE_KN + cp];

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[I_PIECE_KN + cp][dest];

			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];
			mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[dest]][dest];

			mv->board[MOVE_TO]->brd[dest] 	= I_PIECE_KN + cp;
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_PROMOTE_KN | I_MOVE_CAPTURE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;

			// promote to queen (bishop and rook are underpromotions)
			mv = setup_move(p);

			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;
			mv->board[MOVE_TO]->pwn[cp]		= mv->board[MOVE_FROM]->pwn[cp] - 1;

			mv->board[MOVE_TO]->mat[cp] 	-= GAME.PIECE_VALUE[this_piece];
			mv->board[MOVE_TO]->mat[cp] 	+= GAME.PIECE_VALUE[I_PIECE_QU + cp];

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[I_PIECE_QU + cp][dest];

			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];
			mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[dest]][dest];

			mv->board[MOVE_TO]->brd[dest] 	= I_PIECE_QU + cp;
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_PROMOTE_QU | I_MOVE_CAPTURE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;
		}
		else
		{
			mv = setup_move(p);
			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

			// if we are taking a pawn, we need to account for it
			if (mv->board[MOVE_FROM]->brd[dest] == otherpawn) --mv->board[MOVE_TO]->pwn[op];

			mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[this_piece][dest];

			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];
			mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[dest]][dest];

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_CAPTURE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;
		}
	}

	// temporary en passant check
	tenp = p->enp - (dir * I_BOARD_RANK);

	// MOVE #5: capture a pawn passing by en passant
	if (p->enp != NO_EN_PASSANT)
	if (tenp == k + 1 || tenp == k - 1)
	{
		// destination of attacking pawn after capturing "en passant" pawn
		dest = p->enp;
		mv = setup_move(p);

		mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

		// we are taking a pawn, and we need to account for it
		--mv->board[MOVE_TO]->pwn[op];

		mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
		mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

		mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
		mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[this_piece][dest];

		mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[otherpawn];
		mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[otherpawn][dest];

		mv->index[MOVE_TO  ]			= dest;
		mv->index[MOVE_FROM]			= k;

		mv->board[MOVE_TO]->halfmoves	= 0;
		mv->str = I_MOVE_CAPTURE_ENPASS;

		// only add this move to the stack if does not cause check
		if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
		else moves.arr[moves.size++] = mv;
	}
};

// add all legal moves for the current players' knight
// knights' legal moves are in the L-shape (one space and two spaces)
void add_legal_moves_KN(I_CHESS_POSITION *p, int k, MOVELIST& moves)
{
	int cp = p->col, dest; I_CHESS_MOVE *mv;
	int op = 1 ^ cp, i, otherpawn = I_PIECE_PA + op;
	
	int const this_piece = I_PIECE_KN + cp;

	// MOVE #1 - #8: moving L-shaped, using an array of predetermined jump sizes
	for (i = 0; i < I_KN_MOVES_SIZE; ++i) if (p->brd[dest = k + I_KN_MOVES[i]] < I_PIECE_VALID)
	{
		bool iscapture = ((p->brd[dest] & 1) == op && p->brd[dest] != I_BOARD_EMPTY);
		if (p->brd[dest] != I_BOARD_EMPTY && !iscapture) continue;

		mv = setup_move(p);
		mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

		// if we are taking a pawn, we need to account for it
		if (mv->board[MOVE_FROM]->brd[dest] == otherpawn) --mv->board[MOVE_TO]->pwn[op];

		if (iscapture)
		{
			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];
			mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[dest]][dest];
		};

		mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
		mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[this_piece][dest];

		mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
		mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

		mv->index[MOVE_TO  ]			= dest;
		mv->index[MOVE_FROM]			= k;

		mv->str = iscapture ? I_MOVE_CAPTURE_NORMAL : I_MOVE_NORMAL;

		// only add this move to the stack if does not cause check
		if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
		else moves.arr[moves.size++] = mv;
	};
};

// add all legal moves for the current players' bishop
// bishops' legal moves are rays along diagonals of the board
void add_legal_moves_BI(I_CHESS_POSITION *p, int k, MOVELIST& moves)
{
	int cp = p->col, dest = k; I_CHESS_MOVE *mv;
	int op = 1 ^ cp, i;
	
	int const this_piece = I_PIECE_BI + cp;
	
	// MOVE(S): diagonals in given directions
	for (i = 0; i < I_BI_MOVES_SIZE; ++i, dest = k)
	{
		while (p->brd[dest + I_BI_MOVES[i]] == I_BOARD_EMPTY)
		{
			dest += I_BI_MOVES[i];
			mv = setup_move(p);

			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

			mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[k]][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[k]][dest];

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;
		}

		dest += I_BI_MOVES[i];
		if ((p->brd[dest] & 1) == op && p->brd[dest] != I_BOARD_INVALID)
		{
			mv = setup_move(p);
			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

			mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[k]][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[k]][dest];

			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];
			mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[dest]][dest];

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_CAPTURE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;
		}
	};
};

// add all legal moves for the current players' rook
// rooks' legal moves are rays along ranks and files of the board
void add_legal_moves_RO(I_CHESS_POSITION *p, int k, MOVELIST& moves)
{
	int cp = p->col, dest = k; I_CHESS_MOVE *mv;
	int op = 1 ^ cp, i;

	int const this_piece = I_PIECE_RO + cp;
	bool isrook = (p->brd[k] == this_piece);

	// MOVE(S): ranks and files in given directions
	for (i = 0; i < I_RO_MOVES_SIZE; ++i, dest = k)
	{
		while (p->brd[dest + I_RO_MOVES[i]] == I_BOARD_EMPTY)
		{
			dest += I_RO_MOVES[i];
			mv = setup_move(p);

			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

			// cancel out castling for this rook
			if (isrook && k == I_PROMROW[op] * I_BOARD_RANK + 2) mv->board[MOVE_TO]->cst |= 1 << (I_CASTLE_QU + cp);
			if (isrook && k == I_PROMROW[op] * I_BOARD_RANK + 9) mv->board[MOVE_TO]->cst |= 1 << (I_CASTLE_KI + cp);

			mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[k]][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[k]][dest];

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;
		}

		dest += I_RO_MOVES[i];
		if ((p->brd[dest] & 1) == op && p->brd[dest] != I_BOARD_INVALID)
		{
			mv = setup_move(p);

			mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;
			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];

			// cancel out castling for this rook
			if (isrook && k == I_PROMROW[op] * I_BOARD_RANK + 2) mv->board[MOVE_TO]->cst |= 1 << (I_CASTLE_QU + cp);
			if (isrook && k == I_PROMROW[op] * I_BOARD_RANK + 9) mv->board[MOVE_TO]->cst |= 1 << (I_CASTLE_KI + cp);

			mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
			mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

			mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[k]][k];
			mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[k]][dest];

			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];
			mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[dest]][dest];

			mv->index[MOVE_TO  ]			= dest;
			mv->index[MOVE_FROM]			= k;

			mv->str = I_MOVE_CAPTURE_NORMAL;

			// only add this move to the stack if does not cause check
			if (in_check(mv->board[MOVE_TO], cp)) free_move(mv);
			else moves.arr[moves.size++] = mv;
		}
	};
};

// add all legal moves for the current players' king
// kings' legal moves are single spaces in any direction, or castling
void add_legal_moves_KI(I_CHESS_POSITION *p, int k, MOVELIST& moves)
{
	int cp = p->col, dest; I_CHESS_MOVE *mv;
	int op = 1 ^ cp, i, otherpawn = I_PIECE_PA + op;
	
	int const this_piece = I_PIECE_KI + cp;

	// MOVE #1 - #8: moving single spaces, using an array of predetermined jump sizes
	for (i = 0; i < I_KI_MOVES_SIZE; ++i) if (p->brd[dest = k + I_KI_MOVES[i]] < I_PIECE_VALID)
	{
		// prevent moving into check (good to catch it now)
		if (under_attack(p, cp, dest)) continue;

		bool iscapture = ((p->brd[dest] & 1) == op && p->brd[dest] < I_PIECE_COUNT);
		if (p->brd[dest] != I_BOARD_EMPTY && !iscapture) continue;

		mv = setup_move(p);

		moves.arr[moves.size++] 		= mv;
		mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

		mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][k];
		mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[this_piece][dest];

		if (iscapture)
		{
			mv->board[MOVE_TO]->mat[op] 	-= GAME.PIECE_VALUE[mv->board[MOVE_FROM]->brd[dest]];
			mv->board[MOVE_TO]->pos[op] 	-= GAME.POS_BOARDS[mv->board[MOVE_FROM]->brd[dest]][dest];

		};

		// cancel out castling entirely for this player
		mv->board[MOVE_TO]->cst |= ((I_CASTLE_KI | I_CASTLE_QU) << cp);

		// if we are taking a pawn, we need to account for it
		if (mv->board[MOVE_FROM]->brd[dest] == otherpawn) --mv->board[MOVE_TO]->pwn[op];

		mv->board[MOVE_TO]->brd[dest] 	= mv->board[MOVE_FROM]->brd[k];
		mv->board[MOVE_TO]->brd[k] 		= I_BOARD_EMPTY;

		mv->index[MOVE_TO  ]			= dest;
		mv->index[MOVE_FROM]			= k;

		mv->board[MOVE_TO]->king[cp] 	= dest;
		mv->str = iscapture ? I_MOVE_CAPTURE_NORMAL : I_MOVE_NORMAL;
	};

	// the rank where the king appears, and whether or not the king is in check
	int backrank = I_BACKROW[cp] * I_BOARD_RANK; bool chk = in_check(p, cp);

	// MOVE #9: castling kingside
	if (!chk && ((p->cst & (I_CASTLE_KI + op)) > 0))
	if (p->brd[backrank + 7] == I_BOARD_EMPTY && p->brd[backrank + 8] == I_BOARD_EMPTY)
	if (!under_attack(p, cp, backrank + 7) && !under_attack(p, cp, backrank + 8))
	{
		mv = setup_move(p);

		moves.arr[moves.size++] 		= mv;
		mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

		// cancel out castling entirely for this player
		mv->board[MOVE_TO]->cst |= ((I_CASTLE_KI | I_CASTLE_QU) << cp);

		mv->board[MOVE_TO]->brd[backrank + 7] 	= I_PIECE_RO + cp;
		mv->board[MOVE_TO]->brd[backrank + 8] 	= this_piece;

		mv->board[MOVE_TO]->brd[backrank + 9] 	= I_BOARD_EMPTY;
		mv->board[MOVE_TO]->brd[backrank + 6] 	= I_BOARD_EMPTY;

		mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][backrank + 6];
		mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[this_piece][backrank + 8];

		mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[I_PIECE_RO + cp][backrank + 9];
		mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[I_PIECE_RO + cp][backrank + 7];

		mv->index[MOVE_TO  ]			= 0;
		mv->index[MOVE_FROM]			= 0;

		mv->board[MOVE_TO]->king[cp] 	= dest;
		mv->str = I_MOVE_CASTLE_KI;
	}

	// MOVE #10: castling queenside
	if (!chk && ((p->cst & (I_CASTLE_QU + op)) > 0))
	if (p->brd[backrank + 4] == I_BOARD_EMPTY && p->brd[backrank + 5] == I_BOARD_EMPTY)
	if (!under_attack(p, cp, backrank + 4) && !under_attack(p, cp, backrank + 5))
	{
		mv = setup_move(p);

		moves.arr[moves.size++] 		= mv;
		mv->board[MOVE_TO]->enp 		= NO_EN_PASSANT;

		// cancel out castling entirely for this player
		mv->board[MOVE_TO]->cst |= ((I_CASTLE_KI | I_CASTLE_QU) << cp);

		mv->board[MOVE_TO]->brd[backrank + 5] 	= I_PIECE_RO + cp;
		mv->board[MOVE_TO]->brd[backrank + 4] 	= this_piece;

		mv->board[MOVE_TO]->brd[backrank + 2] 	= I_BOARD_EMPTY;
		mv->board[MOVE_TO]->brd[backrank + 6] 	= I_BOARD_EMPTY;

		mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[this_piece][backrank + 6];
		mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[this_piece][backrank + 4];

		mv->board[MOVE_TO]->pos[cp] 	-= GAME.POS_BOARDS[I_PIECE_RO + cp][backrank + 2];
		mv->board[MOVE_TO]->pos[cp] 	+= GAME.POS_BOARDS[I_PIECE_RO + cp][backrank + 5];

		mv->index[MOVE_TO  ]			= 0;
		mv->index[MOVE_FROM]			= 0;

		mv->board[MOVE_TO]->king[cp] 	= dest;
		mv->str = I_MOVE_CASTLE_QU;
	}
};

// add all legal moves for the current players' king
// kings' legal moves are single spaces in any direction, or castling
int count_moves_KI(I_CHESS_POSITION *p)
{
	int cp = p->col, k = p->king[cp], dest;
	int op = 1 ^ cp, i, moves = 0; bool iscapture;

	// MOVE #1 - #8: moving single spaces, using an array of predetermined jump sizes
	for (i = 0; i < I_KI_MOVES_SIZE; ++i) if (p->brd[dest = k + I_KI_MOVES[i]] < I_PIECE_VALID)
	{
		// prevent moving into check (good to catch it now)
		if (under_attack(p, cp, dest)) continue;

		iscapture = ((p->brd[dest] & 1) == op && p->brd[dest] < I_PIECE_COUNT);
		if (p->brd[dest] != I_BOARD_EMPTY && !iscapture) continue;

		++moves;
	};

	// the rank where the king appears, and whether or not the king is in check
	int backrank = I_BACKROW[cp] * I_BOARD_RANK; bool chk = in_check(p, cp);

	// MOVE #9: castling kingside
	if (!chk && ((p->cst & (I_CASTLE_KI + op)) > 0) && p->brd[backrank + 7] == I_BOARD_EMPTY
		&& p->brd[backrank + 8] == I_BOARD_EMPTY && !under_attack(p, cp, backrank + 7)
		&& !under_attack(p, cp, backrank + 8)) ++moves;

	// MOVE #10: castling queenside
	if (!chk && ((p->cst & (I_CASTLE_QU + op)) > 0) && p->brd[backrank + 4] == I_BOARD_EMPTY
		&& p->brd[backrank + 5] == I_BOARD_EMPTY && !under_attack(p, cp, backrank + 4)
		&& !under_attack(p, cp, backrank + 5)) ++moves;

	return moves;
};

// finding information regarding whether a square is under attack by opposing pieces
bool under_attack(I_CHESS_POSITION *p, int c, int k)
{
	int i, tk, dir = I_FORWARD[c], op = 1 ^ c;

	// check for attacking pawns
	if (p->brd[k - (dir * (I_BOARD_RANK + 1))] == I_PIECE_PA + op ||
		p->brd[k - (dir * (I_BOARD_RANK - 1))] == I_PIECE_PA + op) return true;

	// check for attacking knights
	for (i = 0; i < I_KN_MOVES_SIZE; ++i)
	if (p->brd[k + I_KN_MOVES[i]] == I_PIECE_KN + op) return true;

	// check for attacking kings
	for (i = 0; i < I_KI_MOVES_SIZE; ++i)
	if (p->brd[k + I_KI_MOVES[i]] == I_PIECE_KI + op) return true;

	// check for attacking rooks and queens
	for (i = 0; i < I_RO_MOVES_SIZE; ++i)
	{
		tk = k; while (p->brd[tk += I_RO_MOVES[i]] == I_BOARD_EMPTY);
		if (p->brd[tk] == I_PIECE_RO + op || p->brd[tk] == I_PIECE_QU + op) return true;
	};

	// check for attacking bishops and queens
	for (i = 0; i < I_BI_MOVES_SIZE; ++i)
	{
		tk = k; while (p->brd[tk += I_BI_MOVES[i]] == I_BOARD_EMPTY);
		if (p->brd[tk] == I_PIECE_BI + op || p->brd[tk] == I_PIECE_QU + op) return true;
	};

	// well, no pieces seem to be in a position to attack, so...
	return false;
}

// determine whether a player is in check
bool in_check(I_CHESS_POSITION *p, int c)
{ return under_attack(p, c, p->king[c]); };

// in check, and all legal moves leave player in check
bool checkmate(I_CHESS_POSITION *p, int c)
{ return in_check(p, c) && no_legal_moves(p, c); };

// not in check, but all legal moves cause check
bool stalemate(I_CHESS_POSITION *p, int c)
{ return !in_check(p, c) && no_legal_moves(p, c); };

// determine whether current player is in check
bool in_check(I_CHESS_POSITION *p)
{ return in_check(p, p->col); };

// determine whether current player is checkmated
bool checkmate(I_CHESS_POSITION *p)
{ return checkmate(p, p->col); };

// determine whether current player is stalemated
bool stalemate(I_CHESS_POSITION *p)
{ return stalemate(p, p->col); };

bool no_legal_moves(I_CHESS_POSITION *p, int c)
{
	// temporary variables
	I_CHESS_POSITION temp;
	int move_count;

	// copy chess position (better way?) and set its color
	memcpy(&temp, p, sizeof(I_CHESS_POSITION));
	temp.col = c;

	// get the number of legal moves
	MOVELIST moves = gen_move_list(&temp);
	move_count = moves.size;

	// free up memory before we return
	free_move_list(moves); return (move_count == 0);
};

// convert full (12x12) board index to true (8x8) board index
inline int convert_144_to_64(int i)
{ return (8 * ((i / 12) - 2)) + ((i % 12) - 2); };

// convert true (8x8) board index to full (12x12) board index
inline int convert_64_to_144(int i)
{ return (12 * ((i / 8) + 2)) + ((i % 8) + 2); };

// for printing board to console or file, through the output stream
std::ostream& operator<<(std::ostream& out, const I_CHESS_POSITION *p)
{
	I_BOARD_2D* b = (I_BOARD_2D*) p->brd;
	for (int row = I_BOARD_BEGIN; row < I_BOARD_END; ++row)
	{
		out << "   +---+---+---+---+---+---+---+---+" << std::endl;
		out << " " << (I_BOARD_END - row);

		for (int col = I_BOARD_BEGIN; col < I_BOARD_END; ++col)
		out << " | " << PIECE_CHARS[(*b)[row][col]];

		out << " |" << std::endl;
	}
	out << "   +---+---+---+---+---+---+---+---+" << std::endl;
	out << "     a   b   c   d   e   f   g   h  " << std::endl;

	return out;
};

// select best move using all available methods
I_CHESS_MOVE apply_best_move(I_CHESS_POSITION *p, int depth)
{
	// get best move
	I_CHESS_MOVE* mv = select_best_move(p, depth);
	
	// get the move
	I_CHESS_MOVE rtn; memcpy(&rtn, mv, sizeof(I_CHESS_MOVE));
	
	// apply move and clean up
	apply_move(p, mv); free_move(mv);
	
	// return the move
	return rtn;
};

// select best move using all available methods
I_CHESS_MOVE* select_best_move(I_CHESS_POSITION *p, int depth)
{
	// begin by deciding whether to clear the history table
	if (rand() % HIST_TBL_MEMORY == 0)
	clear_table_history(GAME.SEARCH_AGENT.hist);

	// invoke the alphabeta driver
	I_CHESS_MOVE *mv = alpha_beta_driver(p, depth);

	// if no move was generated, fatal error
	trace_assert(mv != NULL, "FAILED to generate a valid move from alpha-beta search");

	// return the resulting move
	return mv;
};

I_BITBOARD get_bitboard(I_CHESS_POSITION* p, int type)
{
	// return value and accumulator
	I_BITBOARD return_board = EMPTY_BITBOARD;
	unsigned long long current_flag = 1;

	// iterators
	int i, j;

	// loop through and set the bitboard
	for (i = I_BOARD_BEGIN * I_BOARD_RANK; i < I_BOARD_END * I_BOARD_RANK; i += I_BOARD_RANK)
	for (j = I_BOARD_BEGIN; j < I_BOARD_END; ++j, current_flag <<= 1)
	{
		// if the piece is a pawn, add the flag to the bitboard
		if (is_piece_type(p->brd[i + j], type)) return_board |= current_flag;
	};

	// return the new bitboard
	return return_board;
};

I_BITBOARD get_bitboard(I_CHESS_POSITION* p, int type, int c)
{
	// return value and accumulator
	I_BITBOARD return_board = EMPTY_BITBOARD;
	unsigned long long current_flag = 1;

	// iterators
	int i, j;

	// loop through and set the bitboard
	for (i = I_BOARD_BEGIN * I_BOARD_RANK; i < I_BOARD_END * I_BOARD_RANK; i += I_BOARD_RANK)
	for (j = I_BOARD_BEGIN; j < I_BOARD_END; ++j, current_flag <<= 1)
	{
		// if the piece is our pawn, add the flag to the bitboard
		if (p->brd[i + j] == type + c) return_board |= current_flag;
	};

	// return the new bitboard
	return return_board;
};

// adapted from "http://en.wikipedia.org/wiki/Mersenne_Twister"
void mt_setup(unsigned int seed)
{
	// setup the values to be used; register may be superfluous
	register unsigned int i = MT_LEN; MT_ints[0] = seed;

	// we are going in the opposite direction of the specification
	while (i--) MT_ints[i] = (i + 0x6c078965UL * (MT_ints[i - 1] ^ (MT_ints[i - 1] >> 30))) & 0xffffffff;
};

// following MT19937 algorithm
inline unsigned int mt_rand32()
{
	// generate numbers once we have run out
	if (!MT_index) mt_generate();

	// perform the operations
	register unsigned long y = (unsigned long) MT_ints[--MT_index];
	y ^= y >> 11; y ^= (y << 7) & 0x9D2C5680UL;
	y ^= (y << 15) & 0xEFC60000UL; y ^= y >> 18;

	// return y as a unsigned int
	return (unsigned int)y;
};

inline void mt_generate()
{
	// reset the countdown
	MT_index = MT_LEN;

	// register keyword is probably useless
	register unsigned int i = MT_LEN, y;

	// we are going in the opposite direction of the specification
	while (i--)
	{
		y = (MT_ints[i] & 0x80000000UL) + ((MT_ints[(i + 1) % MT_LEN]) & 0x7fffffffUL);
		MT_ints[i] = MT_ints[(i + 397) % MT_LEN] ^ (y >> 1); if (y % 2) MT_ints[i] ^= 0x9908b0dfUL;
	};
};

inline bool is_power_of_2(int n)
{ return ((n > 0) && ((n & (n - 1)) == 0)); };

inline bool is_piece_type(int piece, int type)
{ return ((piece & (~1)) == type); };

inline int get_piece_type(int piece)
{ return (piece & (~1)); };

inline bool is_white_sq(int n)
{ return is_white_sq(n % I_BOARD_RANK, n / I_BOARD_RANK); };

inline bool is_white_sq(int x, int y)
{ return !(bool)((x + y) & 1); };

// ambiguous distance function
inline int board_distance(int piece, int x1, int y1, int x2, int y2)
{
	// _NOTE_: there was a call to is_white_sq() to ensure that the bishop
	// being examined was one the same color as the square in question (n2).
	// this may not be necessary, since this function is being written
	// primarily for use with distance from king, and he is not restricted
	// to a single color of squares. truncated half-distances are acceptable.

	// if there is ever a need for anything else (legitimate distance), an
	// analogous function using is_white_sq() may be required.

	// for cutoffs
	int const INF_DIST = 1 << 30;

	// different functionality for different pieces
	switch (piece & (~1))
	{
		case I_PIECE_PA: return cheb_distance(x1, y1, x2, y2); break;
		case I_PIECE_KN: return cheb_distance(x1, y1, x2, y2); break;
		case I_PIECE_BI: return taxi_distance_rot(x1, y1, x2, y2); break;
		case I_PIECE_QU: return cheb_distance(x1, y1, x2, y2); break;
		case I_PIECE_RO: return taxi_distance(x1, y1, x2, y2); break;
		case I_PIECE_KI: return cheb_distance(x1, y1, x2, y2); break;
		default: return INF_DIST; break;
	};
};

inline int board_distance(int piece, int n1, int n2)
{ return board_distance(piece, n1 % I_BOARD_RANK, n1 / I_BOARD_RANK, n2 % I_BOARD_RANK, n2 / I_BOARD_RANK); };

// distance function (for rooks)
inline int taxi_distance(int x1, int y1, int x2, int y2)
{ return std::abs(x1 - x2) + std::abs(y1 - y2); };

// distance function (for bishops) (somewhat complicated)
inline int taxi_distance_rot(int x1, int y1, int x2, int y2)
{
	int rot_x1, rot_y1, rot_x2, rot_y2;

	// normalises coordinates along principal diagonals
	rot_x1 = (x1 - y1) >> 1; rot_y1 = (x1 + y1) >> 1;
	rot_x2 = (x2 - y2) >> 1; rot_y2 = (x2 + y2) >> 1;

	return taxi_distance(rot_x1, rot_y1, rot_x2, rot_y2);
};

// distance function (for kings)
inline int cheb_distance(int x1, int y1, int x2, int y2)
{ return std::max(std::abs(x2 - x1), std::abs(y2 - y1)); };

// driver functions for all distance functions
inline int taxi_distance(int n1, int n2)
{ return taxi_distance(n1 % I_BOARD_RANK, n1 / I_BOARD_RANK, n2 % I_BOARD_RANK, n2 / I_BOARD_RANK); };

inline int taxi_distance_rot(int n1, int n2)
{ return taxi_distance_rot(n1 % I_BOARD_RANK, n1 / I_BOARD_RANK, n2 % I_BOARD_RANK, n2 / I_BOARD_RANK); };

inline int cheb_distance(int n1, int n2)
{ return cheb_distance(n1 % I_BOARD_RANK, n1 / I_BOARD_RANK, n2 % I_BOARD_RANK, n2 / I_BOARD_RANK); };
